// Copyright (C) Calum Grant 2008

#include "cppscript"
#include "dynamic/extensions.hpp"
#include "wrap_cpp_container.hpp"

#include <list>
#include <cstring>


static_call( register_pickle_type(cmp_object,internal::string_object()) );

using internal::nonroot_var;


namespace
{
	struct member_cmp
	{
		bool operator()(const char * s1, const char * s2) const
		{
			return strcmp(s1, s2)<0;
		}

		bool operator()(const std::string & s1, const std::string & s2) const
		{
			return s1<s2;
		}
	};


	template<typename KeyType>
	struct object_traits
	{
		typedef std::pair<KeyType const, nonroot_var> value_type;

		typedef std::map<KeyType, nonroot_var, member_cmp, dynamic::allocator<value_type> > container;

		static var key(value_type & i)
		{
			return i.first;
		}

		static var deref(value_type & i)
		{
			return i.first;
		}

		static const char * key(const var & k)	// ?? Surely something else
		{
			return 0;
		}

		static var value(value_type & i)
		{
			return i.second.get();
		}

		static void mark_reachable(value_type & i, gc::garbage_collector & gc)
		{
			i.second.ref().impl().mark_reachable(gc);
		}

		static const var_cmp_index comparison_index = cmp_object;

		static const char * class_name()
		{
			return "object";
		}

		static const char * iterator_class_name()
		{
			return "object::iterator";
		}
	};
}

namespace dynamic
{
namespace types
{
	/// Implements an object variable.
	/** The KeyType is normally a const char*, since in compiled C++, the keys are 
		immutable and const char* is more efficient than a std::string.
		However there are circumstances where key types are not known at 
		compile-time, such as for unpickling, and in the interpreter.  In
		these rare cirsumstances, KeyType is std::string.
		\ingroup impl */
	template<typename KeyType>
	class object_impl : public dynamic::internal::wrap_assoc_container<object_traits<KeyType> >
	{
	private:
		typedef internal::wrap_assoc_container<object_traits<KeyType> > super_type;

		typedef typename super_type::iterator iterator;

	protected:
		KeyType m_class_name;

	public:
		object_impl(const KeyType class_name) : m_class_name(class_name)
		{
		}

		std::string class_name()
		{
			return m_class_name;
		}

		var get_member(const char * k)
		{
			iterator i=this->m_data.find(k);
			if( i == this->m_data.end() )
				throw not_found(k);
			return object_traits<KeyType>::value(*i);
		}

		void set_member(const var & k, const var & v)
		{
			throw not_supported(this, "set_member(var,var)");
		}

		var call(const var&k, const var & v)
		{ 
			throw not_supported(this, "call with 2 arguments");
		}

		int max_args()
		{
			return -1;
		}

		var get_member(const var & v)
		{
			return get_member( v.as_string().c_str() );
		}

		bool contains(const var & v)
		{
			return this->m_data.find(v.as_string().c_str()) != this->m_data.end();
		}

		void erase(const var & v)
		{
			this->m_data.erase(v.as_string().c_str());
		}

		void set_member(const char * k, const var & v)
		{
			this->m_data[k] = v;
			this->invalidate_iterators();	// Is this necessary?
		}

		void pickle(pickler & p)
		{
			p.write_object_type(this->comparison_index());

			p.write_string(m_class_name);
			p.write_int(int(this->m_data.size()));
			for(iterator i=this->m_data.begin(); i!=this->m_data.end(); ++i)
			{
				p.write_string(i->first);
				p.write_object(i->second.ref());
			}
		}


		var member_inc(const char * k)
		{
			iterator i=this->m_data.find(k);
			if( i == this->m_data.end() )
				throw not_found(k);
			++i->second.ref();
			return i->second.get();
		}

		var member_dec(const char * k)
		{
			iterator i=this->m_data.find(k);
			if( i == this->m_data.end() )
				throw not_found(k);
			--i->second.ref();
			return i->second.get();
		}

		var member_add(const char * k, const var & v)
		{
			iterator i=this->m_data.find(k);
			if( i == this->m_data.end() )
				throw not_found(k);
			i->second.ref() += v;
			return i->second.get();
		}

		var member_sub(const char * k, const var & v)
		{
			iterator i=this->m_data.find(k);
			if( i == this->m_data.end() )
				throw not_found(k);
			i->second.ref() -= v;
			return i->second.get();
		}

		var member_inc(const char * k, int)
		{
			iterator i=this->m_data.find(k);
			if( i == this->m_data.end() )
				throw not_found(k);
			var tmp = i->second.get();
			++i->second.ref();
			return tmp;
		}

		var member_dec(const char * k, int)
		{
			iterator i=this->m_data.find(k);
			if( i == this->m_data.end() )
				throw not_found(k);
			var tmp = i->second.get();
			--i->second.ref();
			return tmp;
		}

		void output(ostream & ss)
		{
			ss << m_class_name;
		}

		void output(wostream & ss)
		{
			ss << m_class_name;
		}

		var clone()
		{
			return new object_impl(*this);
		}

		void mixin(const var & other)
		{
			object_impl * other_impl = dynamic_cast<object_impl*>(other.impl().shared_var());

			if(!other_impl) throw not_supported(other, "mixin from");

			for(iterator i=other_impl->m_data.begin(); i!=other_impl->m_data.end(); ++i)
			{
				this->invalidate_iterators();	// Handles edge cases such as mixin from self
				this->m_data.insert(*i);
			}
		}
	};


	/// Implements an object variable with std::string keys.
	/** \ingroup impl */
	class string_object_impl : public object_impl<std::string>
	{
	public:
		string_object_impl(const std::string & classname) : 
			object_impl<std::string>(classname)
		{
		}

		void unpickle(unpickler & p)
		{
			m_class_name = p.read_string();
			int size = p.read_int();
			for(int i=0; i<size; ++i)
			{
				std::string name = p.read_string();
				var v = p.read_object();
				this->m_data.insert( std::make_pair( name, v) );
			}
		}

		var clone()
		{
			return new string_object_impl(*this);
		}
	};
}
}


var dynamic::object()
{
	return new types::object_impl<const char*>("");
}


var dynamic::create_closure(const var & object, const char * method, const var & fn)
{
	return bind(fn, object);
}


var dynamic::object(const char * name)
{
	return new types::object_impl<const char*>(name);
}


var dynamic::internal::string_object(const std::string & name)
{
	return new types::string_object_impl(name);
}


var dynamic::internal::string_object()
{
	return new types::string_object_impl("");
}
